﻿using System;
using System.Configuration;
using System.Net;
using System.Net.Sockets;
using Network.Server.ClientHandling;

namespace Network.Server
{
    public class Server<THandler> : IDisposable
        where THandler : ClientsHandler
    {
        #region PROPERTIES

        public IPEndPoint EndPoint { get; private set; }
        public Action<string> Log { get; set; }

        #endregion

        #region FIELDS

        private bool mTerminate;
        private bool mTerminateGentle;
        private UdpClient mUdpServer;

        #endregion

        #region CONSTRUCTORS

        public Server()
        {
            IPAddress ipAddress;
            if(!string.IsNullOrEmpty(ServerConfigurator.Instance.IPAddress))
            {
                if (!IPAddress.TryParse(ServerConfigurator.Instance.IPAddress, out ipAddress))
                    throw new ConfigurationErrorsException("Illegal IP address set in configuration.");
            }
            else
                ipAddress = IPAddress.Any;

            int port = ServerConfigurator.Instance.Port;

            EndPoint = new IPEndPoint(ipAddress, port);
        }

        public Server(IPEndPoint endPoint)
        {
            EndPoint = endPoint;
        }

        #endregion

        #region PUBLIC METHODS

        public virtual void StartServer()
        {
            AddToLog("Starting server...");
            using (mUdpServer = new UdpClient(EndPoint))
            {
                AddToLog(string.Format("Opening connection to {0}...", EndPoint));
                using (ClientsHandler handler = (ClientsHandler) Activator.CreateInstance(typeof (THandler)))
                {
                    if (Log != null)
                        handler.Log = Log;
                    IPEndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
                    while (!mTerminate || !(mTerminateGentle && handler.ClientsCount == 0))
                    {
                        try
                        {
                            if (mTerminateGentle)
                                handler.Stop(mTerminateGentle);
                            byte[] received = mUdpServer.Receive(ref clientEndPoint);
                            handler.Handle(mUdpServer, clientEndPoint, received);
                        }
                        catch (SocketException)
                        {
                            break;
                        }
                        catch(Exception ex)
                        {
                            AddToLog(string.Format("Exeption occured: \n{0}", ex.Message));
                            break;
                        }
                    }
                    handler.Stop(mTerminateGentle);
                }
            }
        }

        public virtual void StopServer(bool waitForClients = false)
        {
            AddToLog("Stopping server...");
            mTerminate = !waitForClients;
            if(mTerminate)
                mUdpServer.Close();

            mTerminateGentle = waitForClients;
        }

        #endregion

        #region PRIVATE METHODS

        protected void AddToLog(string text)
        {
            if (Log != null)
                Log(text);
        }

        #endregion

        #region Implementation of IDisposable

        public virtual void Dispose()
        {
            AddToLog("Server stoped.");
        }

        #endregion
    }
}
